﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace DienLib
{
    public class Reflector
    {
        /// <summary>
        /// Incase Field inherit from Based Class, have to use GetBasedField instead
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="owner"></param>
        /// <param name="Name"></param>
        /// <returns></returns>
        public static T GetField<T>(object owner, string Name)
        {
            if (owner == null) throw new ArgumentNullException("owner is NULL, can not get Field");
            return (T)owner.GetType().GetField(Name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).GetValue(owner);
        }

        public static T GetNestedField<T>(object owner, string NestedOwner, string Name)
        {
            if (owner == null) throw new ArgumentNullException("owner is NULL, can not get Field");
            return GetField<T>(GetField<object>(owner, NestedOwner), Name);
        }

        /// <summary>
        /// Generic GetBasedField&lt;T1,T2&gt; &amp; cast return type. If don't use this, you can use GetBasedField&lt;BasedType&gt; without cast return type then use &quot;as&quot; to cast
        /// </summary>
        /// <typeparam name="ReturnType"></typeparam>
        /// <typeparam name="BasedType"></typeparam>
        /// <param name="owner"></param>
        /// <param name="BasedName"></param>
        /// <returns></returns>
        public static ReturnType GetBasedField<ReturnType, BasedType>(object owner, string BasedName)
        {
            return (ReturnType)typeof(BasedType).GetField(BasedName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(owner);
        }

        /// <summary>
        /// Replace Generic GetBasedField&lt;T&gt; by Specific Type in Parameter.
        /// </summary>
        /// <param name="basedType"></param>
        /// <param name="owner"></param>
        /// <param name="BasedName"></param>
        /// <returns></returns>
        public static object GetBasedField(Type basedType, object owner, string BasedName)
        {
            return basedType.GetField(BasedName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(owner);
        }

        /// <summary>
        /// Generic GetBasedField&lt;T&gt;
        /// </summary>
        /// <typeparam name="BasedType"></typeparam>
        /// <param name="owner"></param>
        /// <param name="BasedName"></param>
        /// <returns></returns>
        public static object GetBasedField<BasedType>(object owner, string BasedName)
        {
            return typeof(BasedType).GetField(BasedName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(owner);
        }

        /// <summary>
        /// Gerneric SetBasedField&lt;T&gt;
        /// </summary>
        /// <typeparam name="BasedType"></typeparam>
        /// <param name="owner"></param>
        /// <param name="BasedName"></param>
        /// <param name="value"></param>
        public static void SetBasedField<BasedType>(object owner, string BasedName, object value)
        {
            typeof(BasedType).GetField(BasedName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(owner, value);
        }

        /// <summary>
        /// Replace Generic SetBasedField&lt;T&gt; by Specific Type in Parameter
        /// </summary>
        /// <param name="basedType"></param>
        /// <param name="owner"></param>
        /// <param name="BasedName"></param>
        /// <param name="value"></param>
        public static void SetBasedField(Type basedType, object owner, string BasedName, object value)
        {
            basedType.GetField(BasedName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(owner, value);
        }

        /// <summary>
        /// Set for None Index Property
        /// </summary>
        /// <param name="owner"></param>
        /// <param name="property"></param>
        /// <param name="value"></param>
        public static void SetProperty<T>(object owner, string property, T value)
        {
            if (owner == null) throw new ArgumentNullException("owner is NULL, can not set Property");
            owner.GetType().GetProperty(property).SetValue(owner, value, null);
        }

        /// <summary>
        /// Get None Index Property
        /// </summary>
        /// <param name="owner"></param>
        /// <param name="property"></param>
        /// <returns></returns>
        public static T GetProperty<T>(object owner, string property)
        {
            if (owner == null) throw new ArgumentNullException("owner is NULL, can not get Property");
#if DEBUG
            PropertyInfo pinfo = owner.GetType().GetProperty(property);
            return (T)pinfo.GetValue(owner, null);
#else
            return (T)owner.GetType().GetProperty(property).GetValue(owner, null);
#endif
        }

        /// <summary>
        /// Get Integer Index Property
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="owner"></param>
        /// <param name="property"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        public static T GetProperty<T>(object owner, string property, int index)
        {
            if (owner == null) throw new ArgumentNullException("owner is NULL, can not get Property");
            return (T)owner.GetType().GetProperty(property, new[] { typeof(int) }).GetValue(owner, new object[] { index }); // new[] { "key" }
        }

        /// <summary>
        /// Get String Index Property
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="owner"></param>
        /// <param name="property"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        public static T GetProperty<T>(object owner, string property, string key)
        {
            if (owner == null) throw new ArgumentNullException("owner is NULL, can not get Property");
            return (T)owner.GetType().GetProperty(property, new[] { typeof(string) }).GetValue(owner, new[] { key });
        }

        /// <summary>
        /// Get This Indexer with Integer Index
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="owner"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        public static T GetThisIndexer<T>(object owner, int index)
        {
            if (owner == null) throw new ArgumentNullException("owner is NULL, can not get Property");
            return (T)owner.GetType().GetProperty("Item", new[] { typeof(int) }).GetValue(owner, new object[] { index }); // new[] { "key" }
        }

        /// <summary>
        /// Get This Indexer with String Index
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="owner"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        public static T GetThisIndexer<T>(object owner, string key)
        {
            if (owner == null) throw new ArgumentNullException("owner is NULL, can not get Property");
            return (T)owner.GetType().GetProperty("Item", new[] { typeof(string) }).GetValue(owner, new[] { key }); // new[] { "key" }
        }

        /// <summary>
        /// In case see "ambiguous match found" because of so many overload method. Use DoOverloadMethod instead
        /// </summary>
        /// <param name="owner"></param>
        /// <param name="method"></param>
        /// <param name="paramValue"></param>
        public static void DoMethod(object owner, string method, object[] paramValue = null)
        {
            if (owner == null) throw new ArgumentNullException("owner is NULL, can not get Method");
            owner.GetType().GetMethod(method, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Invoke(owner, paramValue);
        }

        /// <summary>
        /// In case see "ambiguous match found" because of so many overload method. Use DoOverloadMethod instead
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="owner"></param>
        /// <param name="method"></param>
        /// <param name="paramValue"></param>
        /// <returns></returns>
        public static T DoMethod<T>(object owner, string method, object[] paramValue = null)
        {
            if (owner == null) throw new ArgumentNullException("owner is NULL, can not get Method");
            return (T)owner.GetType().GetMethod(method, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Invoke(owner, paramValue);
        }

        /// <summary>
        /// Use in case see "ambiguous match found" because of so many overload method. Cannot use Traditional DoMethod 
        /// </summary>
        /// <param name="owner"></param>
        /// <param name="method"></param>
        /// <param name="paramType"></param>
        /// <param name="paramValue"></param>
        public static void DoOverloadMethod(object owner, string method, Type[] paramType, object[] paramValue = null)
        {
            if (owner == null) throw new ArgumentNullException("owner is NULL, can not get Overload Method");
#if DEBUG
            System.Reflection.MethodInfo medinfo = owner.GetType().GetMethod(method, paramType); // Seperate for easy DEBUG
            medinfo.Invoke(owner, paramValue);
#else
            owner.GetType().GetMethod(method, paramType).Invoke(owner, paramValue);
#endif
        }

        /// <summary>
        /// Use in case see "ambiguous match found" because of so many overload method. Cannot use Traditional DoMethod 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="owner"></param>
        /// <param name="method"></param>
        /// <param name="paramType"></param>
        /// <param name="paramValue"></param>
        /// <returns></returns>
        public static T DoOverloadMethod<T>(object owner, string method, Type[] paramType, object[] paramValue = null)
        {
            if (owner == null) throw new ArgumentNullException("owner is NULL, can not get Overload Method");
#if DEBUG
            System.Reflection.MethodInfo medinfo = owner.GetType().GetMethod(method, paramType); // Seperate for easy DEBUG
            return (T)medinfo.Invoke(owner, paramValue);
#else
            return (T)owner.GetType().GetMethod(method, paramType).Invoke(owner, paramValue);
#endif
        }

        public static object DoBasedMethod<BasedType>(object owner, string method, object[] paramValue = null)
        {
            if (owner == null) throw new ArgumentNullException("owner is NULL, can not get Method");
            return typeof(BasedType).GetMethod(method, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Invoke(owner, paramValue);
        }

        public static object DoBasedMethod(Type BasedType, object owner, string method, object[] paramValue = null)
        {
            if (owner == null) throw new ArgumentNullException("owner is NULL, can not get Method");
            return BasedType.GetMethod(method, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Invoke(owner, paramValue);
        }
    }
}